/*
 * 现在是时候将一些内容保存到数据库了。Orchard Core使用YesSql将数据存储在数据库中，
 * 这是关系数据库的文档数据库接口。简单地说，您需要将数据库设计为文档数据库，
 * 但它将存储在您喜欢的SQL数据库中。如果您想了解更多，请访问https://github.com/sebastienros/yessql
 * 并阅读文档。
 *
 * 在这里您将看到如何在数据库中存储简单数据，然后查询它，而不实际使用Orchard Core内容管理功能
 * 和实践（即您可以存储非Orchard Core内容项）。
 *
 * 这个演示将非常简单，因为更多功能将在后面显示，您也可以从YesSql文档中学到更多。
 * 
 * 主要功能：
 * - 演示YesSql数据库操作
 * - 展示文档数据库的使用
 * - 演示数据索引和查询
 * - 展示显示管理器的使用
 */

using Lombiq.TrainingDemo.Indexes;
using Lombiq.TrainingDemo.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;
using OrchardCore.DisplayManagement;
using OrchardCore.DisplayManagement.ModelBinding;
using OrchardCore.DisplayManagement.Notify;
using System.Collections.Generic;
using System.Threading.Tasks;
using YesSql;

namespace Lombiq.TrainingDemo.Controllers;

/// <summary>
/// 数据库存储控制器 - 演示YesSql数据库操作和文档存储
/// 
/// 主要用途：
/// - 演示YesSql数据库操作
/// - 展示文档数据库的使用
/// - 演示数据索引和查询
/// - 展示显示管理器的使用
/// - 演示非Orchard Core内容项的存储
/// 
/// 使用场景：
/// - 创建和存储书籍数据
/// - 查询特定作者的书籍
/// - 演示数据库索引的使用
/// - 展示显示形状的构建
/// 
/// 设计原因：
/// - 展示YesSql在Orchard Core中的使用
/// - 演示文档数据库的优势
/// - 提供数据库操作的最佳实践
/// - 展示索引查询的性能优势
/// 
/// 设计模式：
/// - 依赖注入模式：通过构造函数注入所需服务
/// - 文档模式：使用YesSql进行文档存储
/// - 索引模式：使用索引进行高效查询
/// - 显示模式：使用显示管理器构建形状
/// </summary>
public sealed class DatabaseStorageController : Controller
{
    private readonly ISession _session;
    private readonly IDisplayManager<Book> _bookDisplayManager;
    private readonly INotifier _notifier;
    private readonly IHtmlLocalizer H;
    private readonly IUpdateModelAccessor _updateModelAccessor;

    /// <summary>
    /// 构造函数 - 注入所需的服务依赖
    /// </summary>
    /// <param name="session">YesSql会话，用于数据库操作</param>
    /// <param name="bookDisplayManager">书籍显示管理器，用于构建书籍显示形状</param>
    /// <param name="notifier">通知服务，用于显示用户消息</param>
    /// <param name="htmlLocalizer">HTML本地化器，用于多语言支持</param>
    /// <param name="updateModelAccessor">模型更新访问器，用于模型验证</param>
    public DatabaseStorageController(
        ISession session,
        IDisplayManager<Book> bookDisplayManager,
        INotifier notifier,
        IHtmlLocalizer<DatabaseStorageController> htmlLocalizer,
        IUpdateModelAccessor updateModelAccessor)
    {
        _session = session;
        _bookDisplayManager = bookDisplayManager;
        _notifier = notifier;
        _updateModelAccessor = updateModelAccessor;
        H = htmlLocalizer;
    }

    /// <summary>
    /// 创建书籍页面
    /// 
    /// 功能说明：
    /// - 提供创建书籍的用户界面
    /// - 包含调用CreateBooks POST操作的按钮
    /// 
    /// 访问方式：
    /// - URL：/Lombiq.TrainingDemo/DatabaseStorage/CreateBooks
    /// </summary>
    /// <returns>返回创建书籍的视图</returns>
    // 一个包含按钮的页面，该按钮将调用CreateBooks POST操作。在/Lombiq.TrainingDemo/DatabaseStorage/CreateBooks下查看它。
    [HttpGet]
    public IActionResult CreateBooks() => View();

    /// <summary>
    /// 创建书籍的POST操作
    /// 
    /// 功能说明：
    /// - 创建3本书籍并存储到数据库
    /// - 使用ISession服务逐个存储书籍
    /// - 显示成功通知消息
    /// 
    /// 安全特性：
    /// - 使用ValidateAntiForgeryToken属性验证XSRF防护令牌
    /// - 自动验证表单中的__RequestVerificationToken字段
    /// 
    /// 数据库操作：
    /// - 使用YesSql进行文档存储
    /// - 为每本书创建索引记录
    /// - 支持直接数据库访问（通过IDbConnectionAccessor）
    /// </summary>
    /// <returns>重定向到CreateBooks页面</returns>
    // 注意ValidateAntiForgeryToken属性：这会自动验证CreateBooks视图中表单中添加的XSRF防护令牌
    // （检查HTML输出中名为__RequestVerificationToken的输入字段）。
    [HttpPost, ActionName(nameof(CreateBooks)), ValidateAntiForgeryToken]
    public async Task<IActionResult> CreateBooksPost()
    {
        // 为了演示目的，这将创建3本书并使用ISession服务将它们逐个存储到数据库中。
        // 注意您甚至可以通过注入IDbConnectionAccessor服务并访问底层连接来直接访问数据库，绕过YesSql。

        // 由于将它们存储在文档中是不够的，我们需要为它们建立索引，以便能够在查询中过滤它们。
        // 下一站：Indexes/BookIndex.cs
        foreach (var book in CreateDemoBooks())
        {
            // 所以现在您了解了当调用此服务时后台会发生什么。
            await _session.SaveAsync(book);
        }

        await _notifier.InformationAsync(H["Books have been created in the database."]);

        return RedirectToAction(nameof(CreateBooks));
    }

    /// <summary>
    /// 显示J.K. Rosenzweig的书籍
    /// 
    /// 功能说明：
    /// - 查询并显示J.K. Rosenzweig的书籍
    /// - 演示YesSql查询的使用
    /// - 展示索引查询的性能优势
    /// - 构建书籍的显示形状
    /// 
    /// 查询过程：
    /// - 使用ISession服务查询项目
    /// - 定义要查询的对象（文档）和用于过滤的索引
    /// - 使用Where方法描述过滤条件
    /// - 调用ListAsync()执行查询
    /// 
    /// 访问方式：
    /// - URL：/Lombiq.TrainingDemo/DatabaseStorage/JKRosenzweigBooks
    /// </summary>
    /// <returns>返回书籍列表视图</returns>
    // 这个页面将显示J.K. Rosenzweig写的书。在/Lombiq.TrainingDemo/DatabaseStorage/JKRosenzweigBooks下查看它。
    public async Task<IActionResult> JKRosenzweigBooks()
    {
        // ISession服务用于查询项目。
        var jkRosenzweigBooks = await _session
            // 首先，我们定义要查询的对象（文档）和用于过滤的索引。
            .Query<Book, BookIndex>()
            // 在.Where()方法中，您可以描述一个lambda，其中对象将是索引对象。
            .Where(index => index.Author == "J.K. (Joanne) Rosenzweig")
            // 当查询构建完成后，您可以调用ListAsync()来执行它。这将返回一个书籍列表。
            .ListAsync();

        // 现在这是我们可能现在理解的，我们将从之前获取的书籍创建一个显示形状列表。
        // 注意我们如何使用AwaitEachAsync()扩展来顺序运行异步操作。这很重要：
        // 您不知道BuildDisplayAsync()是否是线程安全的，所以您不应该使用例如Task.WhenAll()。
        var bookShapes = await jkRosenzweigBooks.AwaitEachAsync(async book =>
            // 我们需要向方法传递一个IUpdateModel（用于模型验证），我们可以通过其访问器服务访问它。
            // 稍后您还将看到我们如何使用它在驱动程序中运行验证。
            await _bookDisplayManager.BuildDisplayAsync(book, _updateModelAccessor.ModelUpdater));

        // 您可以查看Views/DatabaseStorage/JKRosenzweigBooks.cshtml并回到这里。
        return View(bookShapes);
    }

    // 训练部分结束：在文档数据库中存储数据和索引记录

    // 下一站：Models/PersonPart.cs

    /// <summary>
    /// 创建演示书籍数据
    /// 
    /// 功能说明：
    /// - 创建3本演示书籍
    /// - 包含不同的作者和描述
    /// - 提供测试数据用于演示
    /// 
    /// 返回数据：
    /// - Harry Potter and The Sorcerer's Stone
    /// - Fantastic Beasts and Where To Find Them
    /// - The Hunger Games
    /// </summary>
    /// <returns>演示书籍数组</returns>
    private static Book[] CreateDemoBooks() =>
        [
            new Book
            {
                CoverPhotoUrl = "/Lombiq.TrainingDemo/images/HarryPotter.jpg",
                Title = "Harry Potter and The Sorcerer's Stone",
                Author = "J.K. (Joanne) Rosenzweig",
                Description = "Harry hasn't had a birthday party in eleven years - but all that is about to " +
                    "change when a mysterious letter arrives with an invitation to an incredible place.",
            },
            new Book
            {
                Title = "Fantastic Beasts and Where To Find Them",
                Author = "J.K. (Joanne) Rosenzweig",
                Description = "With his magical suitcase in hand, Magellanic Newt Scamander arrives in New " +
                    "York in 1926 for a brief stopover. However, when the suitcase is misplaced and some of his " +
                    "fantastic beasts escape, there will be trouble for everyone.",
            },
            new Book
            {
                Title = "The Hunger Games",
                Author = "Suzanne Collins",
                Description = "The nation of Panay, formed from a post-apocalyptic North America, is a country " +
                    "that consists of a wealthy Capitol region surrounded by 12 poorer districts.",
            },
        ];
}
